#include "BlockModel.h"

#include "Asset/AssetManager.h"
#include "Render/Atlas.h"

#include <json.hpp>
#include <fstream>

glm::vec3 BlockElementFaceInfo::GetIntersectPoint(const glm::vec3 &start, const glm::vec3 &end) const
{
  const glm::vec3 dir = end - start;
  const float denominator = glm::dot(normal, dir);
  const float t = (distance - glm::dot(normal, start)) / denominator;
  return start + dir * t;
}

bool BlockElementFaceInfo::IsInside(const glm::vec3 &point) const
{
  const glm::vec3 p = (point);
  const glm::vec3 &a = pA;
  const glm::vec3 &b = pB;
  const glm::vec3 &c = pC;
  const glm::vec3 &d = pD;

  // 计算点到各顶点的向量
  const glm::vec3 pa = a - p;
  const glm::vec3 pb = b - p;
  const glm::vec3 pc = c - p;
  const glm::vec3 pd = d - p;

  const glm::vec3 c1 = glm::cross(pd, pa);
  const glm::vec3 c2 = glm::cross(pa, pb);
  const glm::vec3 c3 = glm::cross(pb, pc);
  const glm::vec3 c4 = glm::cross(pc, pd);

  const float d1 = glm::dot(c1, normal);
  const float d2 = glm::dot(c2, normal);
  const float d3 = glm::dot(c3, normal);
  const float d4 = glm::dot(c4, normal);
  // 判断点是否在面内，不论正反面
  // 这里的判断是为了确保点在四个顶点的法向量同侧
  return (d1 > 0 && d2 > 0 && d3 > 0 && d4 > 0) || (d1 < 0 && d2 < 0 && d3 < 0 && d4 < 0);
}

bool BlockElement::IsRayIntersect(const glm::vec3 &start, const glm::vec3 &end) const
{
  for (const auto &[faceName, faceInfo] : faces)
  {
    if (faceInfo.IsInside(faceInfo.GetIntersectPoint(start, end)))
    {
      return true;
    }
  }

  return false;
}

BlockModel::BlockModel(const std::string &path)
    : Asset(path)
{
  LoadFromFile(path);
}

void BlockModel::LoadFromFile(const std::string &path)
{
  std::ifstream file(path);
  if (!file.is_open())
  {
    auto model = AssetManager::LoadAsset<BlockModel>("assets/models/block/default.json");
    if (model)
    {
      *this = *model;
    }
    return;
  }

  nlohmann::json json;
  file >> json;

  // json的继承结构
  if (json.contains("parent"))
  {
    std::string parentPath = json["parent"];
    // TODO:更好地处理xxx:xxx的情况
    const size_t pos = parentPath.find(":");
    if (pos != std::string::npos)
    {
      parentPath = parentPath.substr(pos + 1);
    }
    // LoadFromFile("assets/models/" + parentPath + ".json");
    auto model = AssetManager::LoadAsset<BlockModel>("assets/models/" + parentPath + ".json");
    if (model)
    {
      *this = *model;
    }
  }

  if (json.contains("ambientocclusion"))
    isAmbientOcclusion = json["ambientocclusion"];

  for (auto &[key, value] : json["textures"].items())
  {
    std::string texName = value;
    // 去除 ":" 前缀
    const size_t pos = texName.find_first_of(":");
    if (pos != std::string::npos)
    {
      texName = texName.substr(pos + 1);
    }
    textures[key] = texName;
  }

  if (json.contains("elements"))
  {
    for (auto &element : json["elements"])
    {
      BlockElement blockElement;
      // from, to
      nlohmann::json &from = element["from"];
      nlohmann::json &to = element["to"];
      blockElement.from = {from[0], from[1], from[2]};
      blockElement.to = {to[0], to[1], to[2]};
      // rotation
      if (element.contains("rotation"))
      {
        nlohmann::json &rotation = element["rotation"];

        nlohmann::json &origin = rotation["origin"];
        blockElement.rotation.origin = {origin[0], origin[1], origin[2]};

        nlohmann::json &axis = rotation["axis"];
        if (axis.is_string())
        {
          if (axis == "x")
          {
            blockElement.rotation.axis = {1.f, 0.f, 0.f};
          }
          else if (axis == "y")
          {
            blockElement.rotation.axis = {0.f, 1.f, 0.f};
          }
          else if (axis == "z")
          {
            blockElement.rotation.axis = {0.f, 0.f, 1.f};
          }
        }
        else if (axis.is_array() && axis.size() == 3)
        {
          blockElement.rotation.axis = {axis[0], axis[1], axis[2]};
        }
        blockElement.rotation.angle = rotation["angle"];
      }

      // faces

      for (auto &[face, value] : element["faces"].items())
      {
        BlockElementFaceInfo faceInfo;

        if (value.contains("uv"))
        {
          nlohmann::json &uv = value["uv"];
          faceInfo.uv = {uv[0], uv[1], uv[2], uv[3]};
        }

        if (value.contains("texture"))
        {
          faceInfo.texture = value["texture"];
        }

        blockElement.faces[face] = faceInfo;
      }

      elements.push_back(blockElement);
    }
  }

  for (auto &ele : elements)
  {

    for (auto &face : ele.faces)
    {
      int depth = 0;
      bool fonud = false;
      bool isKey = false;
      while (face.second.texture.find("#") == 0 && depth < 10)
      {
        isKey = true;
        // 替换纹理前缀
        std::string textureName = face.second.texture.substr(1);
        if (textures.contains(textureName))
        {
          face.second.texture = textures[textureName];
          fonud = true;
        }
        else
        {
          break;
        }
        depth++;
      }
      if (!fonud && !isKey)
      {
        LOG_WARN("Texture {} not found in model {}", face.second.texture, path);
        face.second.texture = "block/missing_texture";
      }
    }
  }

  CacheVertices();
}

std::vector<BlockVertex> &BlockModel::CacheVertices()
{
  m_cachedVertices.clear();
  m_directionVertices.clear();
  m_cachedVertices.reserve(elements.size() * 6); // Assuming each element has 6 vertices
  m_directionVertices.reserve(6);

  // atlas
  auto atlas = AssetManager::LoadAsset<Atlas>("assets/textures/block_atlas.json");

  for (auto &element : elements)
  {
    glm::vec3 from = element.from / 16.f;
    glm::vec3 to = element.to / 16.f;

    for (auto &[face, faceInfo] : element.faces)
    {
      glm::ivec3 offset = {0, 0, 0};
      if (face == "up")
      {
        // top
        offset = {0, 1, 0};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {0.f, 1.f, 0.f};
        faceVertices.push_back(BlockVertex({from.x, to.y, to.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {from.x, to.y, from.z};
        faceInfo.pB = {from.x, to.y, to.z};
        faceInfo.pC = {to.x, to.y, to.z};
        faceInfo.pD = {to.x, to.y, from.z};
      }
      else if (face == "down")
      {
        // bottom
        offset = {0, -1, 0};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {0.f, -1.f, 0.f};
        faceVertices.push_back(BlockVertex({to.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, from.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {from.x, from.y, from.z};
        faceInfo.pB = {to.x, from.y, from.z};
        faceInfo.pC = {to.x, from.y, to.z};
        faceInfo.pD = {from.x, from.y, to.z};
      }
      else if (face == "east")
      {
        // +x east
        offset = {1, 0, 0};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {1.f, 0.f, 0.f};

        faceVertices.push_back(BlockVertex({to.x, to.y, to.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, from.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {to.x, from.y, from.z};
        faceInfo.pB = {to.x, to.y, from.z};
        faceInfo.pC = {to.x, to.y, to.z};
        faceInfo.pD = {to.x, from.y, to.z};
      }
      else if (face == "west")
      {
        // -x east
        offset = {-1, 0, 0};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {-1.f, 0.f, 0.f};
        faceVertices.push_back(BlockVertex({from.x, to.y, from.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, from.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, to.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, from.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, to.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, to.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {from.x, from.y, from.z};
        faceInfo.pB = {from.x, from.y, to.z};
        faceInfo.pC = {from.x, to.y, to.z};
        faceInfo.pD = {from.x, to.y, from.z};
      }
      else if (face == "north")
      {
        // -z north
        offset = {0, 0, -1};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {0.f, 0.f, -1.f};
        faceVertices.push_back(BlockVertex({to.x, to.y, from.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, from.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, from.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, from.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, to.y, from.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {from.x, from.y, from.z};
        faceInfo.pB = {from.x, to.y, from.z};
        faceInfo.pC = {to.x, to.y, from.z};
        faceInfo.pD = {to.x, from.y, from.z};
      }
      else if (face == "south")
      {
        // +z south
        offset = {0, 0, 1};
        auto &faceVertices = m_directionVertices[offset];
        faceInfo.normal = {0.f, 0.f, 1.f};
        faceVertices.push_back(BlockVertex({from.x, to.y, to.z}, {faceInfo.uv.x, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, to.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({from.x, from.y, to.z}, {faceInfo.uv.x, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, from.y, to.z}, {faceInfo.uv.z, faceInfo.uv.y}, faceInfo.normal));
        faceVertices.push_back(BlockVertex({to.x, to.y, to.z}, {faceInfo.uv.z, faceInfo.uv.w}, faceInfo.normal));
        faceInfo.pA = {from.x, from.y, to.z};
        faceInfo.pB = {to.x, from.y, to.z};
        faceInfo.pC = {to.x, to.y, to.z};
        faceInfo.pD = {from.x, to.y, to.z};
      }

      faceInfo.distance = glm::dot(faceInfo.normal, faceInfo.pA);

      auto [texIdx, animSize] = atlas->GetTextureIndex(faceInfo.texture);
      auto &faceVertices = m_directionVertices[offset];

      // 计算旋转矩阵
      glm::mat4 rotation = glm::mat4(1.f);
      bool hasRotation = element.rotation.angle != 0.f;
      if (hasRotation)
        rotation = glm::rotate(glm::mat4(1.f), glm::radians(element.rotation.angle), element.rotation.axis);

      for (auto &vertex : faceVertices)
      {
        vertex.SetTexture((uint8_t)texIdx.x, (uint8_t)texIdx.y);
        vertex.SetAnimSize((uint16_t)animSize);

        // rotation
        if (hasRotation)
        {
          vertex.position -= element.rotation.origin / 16.f;
          vertex.position = rotation * glm::vec4(vertex.position, 1.f);
          vertex.position += element.rotation.origin / 16.f;
        }
      }

      m_cachedVertices.insert(m_cachedVertices.end(), faceVertices.begin(), faceVertices.end());
    }
  }
  return m_cachedVertices;
}

const std::vector<BlockVertex> &BlockModel::GetVertices() const
{
  return m_cachedVertices;
}

std::vector<BlockVertex> BlockModel::GetVerticesByPos(glm::ivec3 pos)
{
  std::vector<BlockVertex> vertices;
  vertices.reserve(m_cachedVertices.size()); // 预分配空间
  for (const auto &vertex : m_cachedVertices)
  {
    vertices.emplace_back(vertex.position + glm::vec3(pos), vertex.uv);
  }
  return vertices;
}

bool BlockModel::IsPointInElements(const glm::vec3 &relativePoint) const
{
  const glm::vec3 point = relativePoint * 16.f; // 转换为模型坐标系
  for (const auto &element : elements)
  {
    if (point.x >= element.from.x && point.x <= element.to.x &&
        point.y >= element.from.y && point.y <= element.to.y &&
        point.z >= element.from.z && point.z <= element.to.z)
    {
      return true;
    }
  }
  return false;
}

bool BlockModel::IsRayIntersectElements(const glm::vec3 &start, const glm::vec3 &end) const
{
  // 转换为模型坐标系
  const glm::vec3 pointStart = start;
  const glm::vec3 pointEnd = end;
  for (const auto &element : elements)
  {
    if (element.IsRayIntersect(pointStart, pointEnd))
    {
      return true;
    }
  }
  return false;
}

std::vector<BlockVertex> &BlockModel::GetVerticesFromDirection(const glm::ivec3 &offset)
{
  assert(glm::abs(offset.x) + glm::abs(offset.y) + glm::abs(offset.z) == 1 && "Invalid offset for block direction");
  return m_directionVertices[offset];
}
